﻿/*
DotNetMQ - A Complete Message Broker For .NET
Copyright (C) 2011 Halil ibrahim KALKAN

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Reflection;
using System.Text;

namespace MDS.Client.MDSServices
{
    /// <summary>
    /// Any MDSService class must be inherited from this class.
    /// </summary>
    public abstract class MDSService
    {
        #region Public properties

        /// <summary>
        /// When a method of a MDSService application is invoked, this field stores address of source application in MDS.
        /// </summary>
        public MDSRemoteAppEndPoint RemoteApplication { get; internal set; }

        /// <summary>
        /// When a method of a MDSService application is invoked, this field stores the original message that is sent by MDS server.
        /// </summary>
        public IIncomingMessage IncomingMessage { get; internal set; }

        #endregion

        #region Predefined Public Service Methods

        /// <summary>
        /// This method generates client proxy class to use this service.
        /// It is also a MDSServiceMethod, so, clients can update it's proxy classes via calling this method remotely.
        /// </summary>
        /// <param name="namespaceName">Namespace of generating proxy class</param>
        /// <returns>Proxy class code to use this service</returns>
        [MDSServiceMethod(Description = "This method generates client proxy class to use this service. Clients can update it's proxy classes via calling this method remotely.")]
        [return: MDSServiceMethodParameter("Proxy class code to use this service")]
        public string GenerateProxyClass([MDSServiceMethodParameter("Namespace of generating proxy class")] string namespaceName)
        {
            //Check parameters
            if (string.IsNullOrEmpty(namespaceName))
            {
                namespaceName = "MDSServiceProxies";
            }

            //Get this Type, Methods and Attributes
            var serviceType = GetType();
            var methods = serviceType.GetMethods();
            var attributes = serviceType.GetCustomAttributes(typeof(MDSServiceAttribute), true);

            //Check for MDSService attribute
            if (attributes.Length <= 0)
            {
                return "This class has not MDSService attribute. So, it is not a MDSService.";
            }

            //Get MDSService attribute
            var mdsServiceAttribute = (MDSServiceAttribute)attributes[0];

            //Generate class name
            var proxyClassName = serviceType.Name + "Proxy";

            //Start generating code
            var classBuilder = new StringBuilder();

            //Generate header of file.
            classBuilder.AppendLine("/* This code file is generated by MDSService Proxy Generator tool.");
            classBuilder.AppendLine(" * ");
            classBuilder.AppendLine(" * Service Name    : " + serviceType.Name);
            classBuilder.AppendLine(" * Service version : " + mdsServiceAttribute.Version);
            classBuilder.AppendLine(" * Generating date : " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            classBuilder.AppendLine(" */");
            classBuilder.AppendLine();

            //Namespaces
            classBuilder.AppendLine("using System;");
            classBuilder.AppendLine("using MDS.Client;");
            classBuilder.AppendLine("using MDS.Client.MDSServices;");
            classBuilder.AppendLine();

            //Class code
            classBuilder.AppendLine("namespace " + namespaceName);
            classBuilder.AppendLine("{");
            classBuilder.AppendLine("    /// <summary>");
            classBuilder.AppendLine("    /// This class is a proxy class to use " + serviceType.Name + " service.");
            if (!string.IsNullOrEmpty(mdsServiceAttribute.Description))
            {
                classBuilder.AppendLine("    /// Service Description: " + mdsServiceAttribute.Description);
            }

            classBuilder.AppendLine("    /// </summary>");
            classBuilder.AppendLine("    public partial class " + proxyClassName + " : MDSServiceProxyBase");
            classBuilder.AppendLine("    {");

            //Constructor
            classBuilder.AppendLine("        #region Constructor");
            classBuilder.AppendLine("        ");
            classBuilder.AppendLine("        /// <summary>");
            classBuilder.AppendLine("        /// Creates a new instance of " + proxyClassName + ".");
            classBuilder.AppendLine("        /// </summary>");
            classBuilder.AppendLine("        /// <param name=\"serviceConsumer\">Reference to a MDSServiceConsumer object to send/receive MDS messages</param>");
            classBuilder.AppendLine("        /// <param name=\"remoteEndPoint\">Remote application end point to send requests</param>");
            classBuilder.AppendLine("        public " + proxyClassName + "(MDSServiceConsumer serviceConsumer, MDSRemoteAppEndPoint remoteEndPoint)");
            classBuilder.AppendLine("            : base(serviceConsumer, remoteEndPoint, \"" + serviceType.Name + "\")");
            classBuilder.AppendLine("        {");
            classBuilder.AppendLine("            ");
            classBuilder.AppendLine("        }");
            classBuilder.AppendLine("        ");
            classBuilder.AppendLine("        #endregion");
            classBuilder.AppendLine("        ");

            //Methods
            classBuilder.AppendLine("        #region " + serviceType.Name + " methods");
            classBuilder.AppendLine("        ");
            foreach (var method in methods)
            {
                if (IsPredefinedMethod(method.Name))
                {
                    continue;
                }

                WriteMethod(classBuilder, method);
            }

            classBuilder.AppendLine("        #endregion");
            classBuilder.AppendLine("        ");
            classBuilder.AppendLine("        #region Default (predefined) service methods");
            classBuilder.AppendLine("        ");
            foreach (var method in methods)
            {
                if (!IsPredefinedMethod(method.Name))
                {
                    continue;
                }

                WriteMethod(classBuilder, method);
            }

            classBuilder.AppendLine("        #endregion");

            //Close class
            classBuilder.AppendLine("    }");

            //Close namespace
            classBuilder.AppendLine("}");

            return classBuilder.ToString();
        }

        /// <summary>
        /// This method can be used to check if service is available.
        /// </summary>
        /// <param name="message">A string message</param>
        /// <returns>Reply to message as formatted: "RE:message".</returns>
        [MDSServiceMethod(Description = "This method can be used to check if service is available.")]
        [return: MDSServiceMethodParameter("Reply to message as formatted: 'RE: message'")]
        public string CheckServiceIsAvailable([MDSServiceMethodParameter("A message to reply")] string message)
        {
            return ("RE: " + message);
        }

        #endregion

        #region Private methods

        private static void WriteMethod(StringBuilder classBuilder, MethodInfo method)
        {
            //Check for MDSServiceMethod attribute
            var methodAttributes = method.GetCustomAttributes(typeof(MDSServiceMethodAttribute), true);
            if (methodAttributes.Length <= 0)
            {
                return;
            }

            //Get MDSServiceMethod attribute
            var serviceMethodAttribute = (MDSServiceMethodAttribute)methodAttributes[0];

            //Get return type
            var returnType = NormalizeType(method.ReturnType.Name);

            //Get parameters
            var parameters = method.GetParameters();

            //Generate proxy method arguments and invoke method parameters
            var methodArgumentsString = new StringBuilder();
            var invokeParameters = new StringBuilder();
            foreach (var parameter in parameters)
            {
                var paramType = NormalizeType(parameter.ParameterType.Name);
                if (methodArgumentsString.Length > 0)
                {
                    methodArgumentsString.Append(", ");
                }

                methodArgumentsString.Append(paramType + " " + parameter.Name);
                invokeParameters.Append(", " + parameter.Name);
            }

            //Generate method summary
            classBuilder.AppendLine("        /// <summary>");
            classBuilder.AppendLine("        /// " + (serviceMethodAttribute.Description ?? "No method summary available."));
            classBuilder.AppendLine("        /// </summary>");

            //Generate XML-Comments for parameters
            foreach (var parameter in parameters)
            {
                var paramAttributes = parameter.GetCustomAttributes(typeof(MDSServiceMethodParameterAttribute), true);
                if (paramAttributes.Length <= 0)
                {
                    continue;
                }

                classBuilder.AppendLine("        /// <param name=\"" + parameter.Name + "\">" + ((MDSServiceMethodParameterAttribute)paramAttributes[0]).Description + "</param>");
            }

            //Generate XML-Comments for return value
            if (returnType != "void")
            {
                var returnAttributes = method.ReturnParameter.GetCustomAttributes(typeof(MDSServiceMethodParameterAttribute), true);
                if (returnAttributes.Length > 0)
                {
                    classBuilder.AppendLine("        /// <returns>" + ((MDSServiceMethodParameterAttribute)returnAttributes[0]).Description + "</returns>");
                }
            }

            //Generate method signature and opening bracket
            classBuilder.AppendLine("        public " + returnType + " " + method.Name + "(" + methodArgumentsString + ")");
            classBuilder.AppendLine("        {");

            //Generate method body according to return value
            if (returnType == "void")
            {
                classBuilder.AppendLine("            InvokeRemoteMethod(\"" + method.Name + "\"" + invokeParameters + ");");
            }
            else
            {
                classBuilder.AppendLine("            return (" + returnType + ") InvokeRemoteMethodAndGetResult(\"" + method.Name + "\"" + invokeParameters + ");");
            }

            //Method closing bracket
            classBuilder.AppendLine("        }");
            classBuilder.AppendLine("        ");
        }

        /// <summary>
        /// Normalizes some known primitive types.
        /// </summary>
        /// <param name="typeName">Type name</param>
        /// <returns>Normalized type name</returns>
        private static string NormalizeType(string typeName)
        {
            switch (typeName)
            {
                case "Void":
                    return "void";
                case "Boolean":
                    return "bool";
                case "Byte":
                    return "byte";
                case "Byte[]":
                    return "byte[]";
                case "Int16":
                    return "short";
                case "Int16[]":
                    return "short[]";
                case "Int32":
                    return "int";
                case "Int32[]":
                    return "int[]";
                case "Int64":
                    return "long";
                case "Int64[]":
                    return "long[]";
                case "String":
                    return "string";
                case "String[]":
                    return "string[]";
                case "Single":
                    return "float";
                case "Single[]":
                    return "float[]";
                case "Double":
                    return "double";
                case "Double[]":
                    return "double[]";
            }

            //Not known type
            return typeName;
        }

        /// <summary>
        /// Checks if a method is predefined method (MDSService methods in MDSService class).
        /// </summary>
        /// <param name="methodName">Method name to check</param>
        /// <returns>True: Yes, it is..</returns>
        private static bool IsPredefinedMethod(string methodName)
        {
            return (methodName == "GenerateProxyClass" || methodName == "CheckServiceIsAvailable");
        }

        #endregion
    }
}
